import type { Path } from 'deep-pick-omit';
import type { PiniaPlugin, PiniaPluginContext, StateTree } from 'pinia';

import { watch } from 'vue';

import { isBoolean } from '@/utils/is';

import { useBroadcastChannel } from '@vueuse/core';
import { deepOmitUnsafe, deepPickUnsafe } from 'deep-pick-omit';
import { destr } from 'destr';
import { MutationType } from 'pinia';

/** 序列化器实现 */
export interface Serializer {
  /**
   * 将字符串反序列化为状态
   * @default destr
   * @see https://github.com/unjs/destr
   */
  deserialize: (data: string) => StateTree;

  /**
   * 将状态序列化为字符串
   * @default JSON.stringify
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
   */
  serialize: (data: StateTree) => string;
}

/** 同步项 */
export interface Synchronization<State extends StateTree = StateTree> {
  /** 订阅回调的类型 */
  mutationTypes?: MutationType[];
  /** 忽略的同步项 */
  omit?: Path<State>[] | string[];
  /** 选择的同步项 */
  pick?: Path<State>[] | string[];
  /** 序列化器 */
  serializer?: Serializer;
}

/** 同步设置 */
export type SynchronizationOptions<State extends StateTree = StateTree> = Partial<
  Synchronization<State>
>;

declare module 'pinia' {
  // 帮助 Pinia 识别类型 @see https://segmentfault.com/q/1010000044068881
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  export interface DefineStoreOptionsBase<S extends StateTree, Store> {
    /** 是否跨实例同步存储
     * 用于解决跨多个浏览器窗口数据存储的同步问题，默认。
     * @param 使用 ‌useBroadcastChannel 解决同源不同页面之间的通信。
     */
    sync?: boolean | SynchronizationOptions<S>;
  }
}

export function createSyncPlugin(): PiniaPlugin {
  return function (context: PiniaPluginContext) {
    const {
      options: { sync = false },
      store,
    } = context;

    if (!sync) {
      return;
    }

    let serializer: Serializer = {
      deserialize: (data) => destr(data),
      serialize: (data) => JSON.stringify(data),
    };
    if (!isBoolean(context?.options?.sync) && context?.options?.sync?.serializer) {
      serializer = context?.options?.sync?.serializer;
    }

    const { data, post } = useBroadcastChannel<string, string>({
      name: `pinia-sync-${store.$id}`,
    });

    // 监听来自其他窗口的消息并更新 store
    watch(data, (newValue) => {
      if (newValue) {
        // const parsedData = JSON.parse(newValue);
        const parsedData = serializer.deserialize(newValue);
        const state: StateTree = { ...store.$state, ...parsedData, ___inner___patch___: true }; // 标注内部更新标识
        store.$patch(state);
      }
    });

    // 监听本窗口内的 store 变化并广播消息
    store.$subscribe(
      (mutation, state) => {
        /*
        mutation主要包含三个属性值：
        events：当前state改变的具体数据，包括改变前的值和改变后的值等等数据
        storeId：是当前store的id
        type：用于记录这次数据变化是通过什么途径，主要有三个分别是
        direct：通过 action 变化的
        patch object：通过 $patch 传递对象的方式改变的
        patch function：通过 $patch 传递函数的方式改变的
        */
        // console.log('mutation', mutation);
        if (state.___inner___patch___) {
          return; // 忽略内部更新标识
        }
        // 我们就可以在此处监听store中值的变化，当变化为某个值的时候，去做一些业务操作之类的
        const syncOption = sync === true ? {} : sync;
        const mutationTypes = syncOption.mutationTypes || [MutationType.direct];
        if (mutationTypes.includes(mutation.type)) {
          const { omit, pick } = syncOption;
          // const picked = pick ? deepPickUnsafe(store.$state, pick) : store.$state;
          const picked: StateTree = pick ? deepPickUnsafe(state, pick) : state;
          const omitted: StateTree = omit ? deepOmitUnsafe(picked, omit) : picked;
          const data: StateTree = deepOmitUnsafe(omitted, ['___inner___patch___']);
          // const deserializeData = JSON.stringify(data);
          const deserializeData = serializer.serialize(data);
          post(deserializeData);
        }
      },
      /*
      第二个参数options对象，是各种配置参数
      detached：布尔值，默认false，正常情况下，当订阅所在的组件被卸载时，订阅将被停止删除；detached值为true时，即使所在组件被卸载，订阅依然在生效。
      参数还有immediate，deep，flush等等参数 和vue3 watch的参数是一样的，多的就不介绍了，用到再看文档吧
      */
      { detached: false },
    );
  };
}
